#include "type.h"
#include "ppu.h"

/**
 * <a href="https://www.nesdev.org/wiki/Status_flags">
 *  About 6502 cpu status info
 *  </a>
 */
typedef enum {
    CARRY,
    ZERO,
    INTERRUPT_DISABLE,
    DECIMAL,
    // (No CPU effect; see: the B flag)
    B_FLAG,
    //(No CPU effect; always pushed as 1)
    EMPTY,
    OVERFLOW,
    NEGATIVE
} StatusBit;


typedef enum {
    UWN,
    ADC, AND, ASL, BCC, BCS, BEQ, BIT, BMI, BNE, BPL, BRK0, BVC, BVS, CLC,
    CLD, CLI, CLV, CMP, CPX, CPY, DEC, DEX, DEY, EOR, INC, INX, INY, JMP,
    JSR, LDA, LDX, LDY, LSR, NOP, ORA, PHA, PHP, PLA, PLP, ROL, ROR, RTI,
    RTS, SBC, SEC, SED, SEI, STA, STX, STY, TAX, TAY, TSX, TXA, TXS, TYA,
    LOG
} Instruction;

static String IStr[] = {
        "UWN",
        "ADC", "AND", "ASL", "BCC", "BCS", "BEQ", "BIT", "BMI", "BNE", "BPL", "BRK", "BVC", "BVS", "CLC",
        "CLD", "CLI", "CLV", "CMP", "CPX", "CPY", "DEC", "DEX", "DEY", "EOR", "INC", "INX", "INY", "JMP",
        "JSR", "LDA", "LDX", "LDY", "LSR", "NOP", "ORA", "PHA", "PHP", "PLA", "PLP", "ROL", "ROR", "RTI",
        "RTS", "SBC", "SEC", "SED", "SEI", "STA", "STX", "STY", "TAX", "TAY", "TSX", "TXA", "TXS", "TYA",
        "LOG"
};

typedef enum {
    NONE,
    Implicit,
    Accumulator,
    Immediate,
    Zero_Page,
    Zero_Page_X,
    Zero_Page_Y,
    Relative,
    Absolute,
    Absolute_X,
    Absolute_Y,
    Indirect,
    Indirect_X,
    Indirect_Y
} AddressMode;

static String AMStr[] = {
        "NONE",
        "Implicit",
        "Accumulator",
        "Immediate",
        "Zero_Page",
        "Zero_Page_X",
        "Zero_Page_Y",
        "Relative",
        "Absolute",
        "Absolute_X",
        "Absolute_Y",
        "Indirect",
        "Indirect_X",
        "Indirect_Y"
};

static uint8 ops[256][4] = {
        /******************* 0x00 **********************/
        {BRK0,  Implicit,    1, 7},
        {ORA,   Indirect_Y,  2, 6},
        {UWN,   NONE,        0, 0},
        {UWN,   NONE,        0, 0},
        {UWN,   NONE,        0, 0},
        {ORA,   Zero_Page,   2, 3},
        {ASL,   Zero_Page,   2, 5},
        {UWN,   NONE,        0, 0},
        {PHP,   Implicit,    1, 8},
        {ORA,   Immediate,   2, 2},
        {ASL,   Accumulator, 1, 2},
        {UWN,   NONE,        0, 0},
        {UWN,   NONE,        0, 0},
        {ORA,   Absolute,    3, 4},
        {ASL,   Absolute,    3, 6},
        {UWN,   NONE,        0, 0},
        /******************* 0x10 **********************/
        {BPL,   Relative,    2, 2},
        {ORA,   Indirect_Y,  2, 5},
        {UWN,   NONE,        0, 0},
        {UWN,   NONE,        0, 0},
        {UWN,   NONE,        0, 0},
        {ORA,   Zero_Page_X, 2, 4},
        {ASL,   Zero_Page_X, 2, 6},
        {UWN,   NONE,        0, 0},
        {CLC,   Implicit,    1, 2},
        {ORA,   Absolute_Y,  3, 4},
        {UWN,   NONE,        0, 0},
        {UWN,   NONE,        0, 0},
        {UWN,   NONE,        0, 0},
        {ORA,   Absolute_X,  3, 4},
        {ASL,   Absolute_X,  3, 7},
        {UWN,   NONE,        0, 0},
        /******************* 0x20 **********************/
        {JSR,   Absolute,    3, 6},
        {AND,   Indirect_X,  2, 6},
        {UWN,   NONE,        0, 0},
        {UWN,   NONE,        0, 0},
        {BIT,   Zero_Page,   2, 3},
        {AND,   Zero_Page,   2, 3},
        {ROL,   Zero_Page,   2, 5},
        {UWN,   NONE,        0, 0},
        {PLP,   Implicit,    1, 4},
        {AND,   Implicit,    2, 2},
        {ROL,   Accumulator, 1, 2},
        {UWN,   NONE,        0, 0},
        {BIT,   Absolute,    3, 4},
        {AND,   Absolute,    3, 4},
        {ROL,   Absolute,    3, 6},
        {UWN,   NONE,        0, 0},
        /******************* 0x30 **********************/
        {BMI,   Relative,    2, 2},
        {AND,   Indirect_Y,  2, 5},
        {UWN,   NONE,        0, 0},
        {UWN,   NONE,        0, 0},
        {UWN,   NONE,        0, 0},
        {AND,   Zero_Page_X, 2, 4},
        {ROL,   Zero_Page_X, 2, 6},
        {UWN,   NONE,        0, 0},
        {SEC,   Implicit,    1, 2},
        {AND,   Absolute_Y,  3, 4},
        {UWN,   NONE,        0, 0},
        {UWN,   NONE,        0, 0},
        {UWN,   NONE,        0, 0},
        {AND,   Absolute_X,  3, 4},
        {ROL,   Absolute_X,  3, 7},
        {UWN,   NONE,        0, 0},
        /******************* 0x40 **********************/
        {RTI,   Implicit,    1, 6},
        {EOR,   Indirect_Y,  2, 6},
        {UWN,   NONE,        0, 0},
        {UWN,   NONE,        0, 0},
        {UWN,   NONE,        0, 0},
        {EOR,   Zero_Page,   2, 3},
        {LSR,   Zero_Page,   2, 5},
        {UWN,   NONE,        0, 0},
        {PHA,   Implicit,    1, 3},
        {EOR,   Immediate,   2, 2},
        {LSR,   Accumulator, 1, 2},
        {UWN,   NONE,        0, 0},
        {JMP,   Absolute,    3, 3},
        {EOR,   Absolute,    3, 4},
        {LSR,   Absolute,    3, 6},
        {UWN,   NONE,        0, 0},
        /******************* 0x50 **********************/
        {BVC,   Relative,    2, 2},
        {EOR,   Indirect_Y,  2, 5},
        {UWN,   NONE,        0, 0},
        {UWN,   NONE,        0, 0},
        {UWN,   NONE,        0, 0},
        {EOR,   Zero_Page_X, 2, 4},
        {LSR,   Zero_Page_X, 2, 6},
        {UWN,   NONE,        0, 0},
        {CLI,   Implicit,    1, 2},
        {EOR,   Absolute_Y,  3, 4},
        {UWN,   NONE,        0, 0},
        {UWN,   NONE,        0, 0},
        {UWN,   NONE,        0, 0},
        {EOR,   Absolute_X,  3, 4},
        {LSR,   Absolute_X,  3, 7},
        {UWN,   NONE,        0, 0},
        /******************* 0x60 **********************/
        {RTS,   Implicit,    1, 6},
        {ADC,   Indirect_X,  2, 6},
        {UWN,   NONE,        0, 0},
        {UWN,   NONE,        0, 0},
        {UWN,   NONE,        0, 0},
        {ADC,   Zero_Page,   2, 3},
        {ROR,   Zero_Page,   2, 5},
        {UWN,   NONE,        0, 0},
        {PLA,   Implicit,    1, 4},
        {ADC,   Immediate,   2, 2},
        {ROR,   Accumulator, 1, 2},
        {UWN,   NONE,        0, 0},
        {JMP,   Indirect,    3, 5},
        {ADC,   Absolute,    3, 4},
        {ROR,   Absolute,    3, 6},
        {UWN,   NONE,        0, 0},
        /********************* 0x70 *******************/
        {BVS,   Relative,    2, 2},
        {ADC,   Indirect_Y,  2, 5},
        {UWN,   NONE,        0, 0},
        {UWN,   NONE,        0, 0},
        {UWN,   NONE,        0, 0},
        {ADC,   Zero_Page_X, 2, 4},
        {ROR,   Zero_Page_X, 2, 6},
        {UWN,   NONE,        0, 0},
        {SEI,   Implicit,    1, 2},
        {ADC,   Absolute_Y,  3, 4},
        {UWN,   NONE,        0, 0},
        {UWN,   NONE,        0, 0},
        {UWN,   NONE,        0, 0},
        {ADC,   Absolute_X,  3, 4},
        {ROR,   Absolute_X,  3, 7},
        {UWN,   NONE,        0, 0},
        /********************* 0x80*******************/
        {UWN,   NONE,        0, 0},
        {STA,   Indirect_Y,  2, 6},
        {UWN,   NONE,        0, 0},
        {UWN,   NONE,        0, 0},
        {STY,   Zero_Page,   2, 3},
        {STA,   Zero_Page,   2, 3},
        {STX,   Zero_Page,   2, 3},
        {UWN,   NONE,        0, 0},
        {DEY,   Implicit,    1, 2},
        {UWN,   NONE,        0, 0},
        {TXA,   Implicit,    1, 2},
        {UWN,   NONE,        0, 0},
        {STY,   Absolute,    3, 4},
        {STA,   Absolute,    3, 4},
        {STX,   Absolute,    3, 4},
        {UWN,   NONE,        0, 0},
        /********************* 0x90*******************/
        {BCC,   Relative,    2, 2},
        {STA,   Indirect_Y,  2, 6},
        {UWN,   NONE,        0, 0},
        {UWN,   NONE,        0, 0},
        {STY,   Zero_Page_X, 2, 4},
        {STA,   Zero_Page_X, 2, 4},
        {STX,   Zero_Page_Y, 2, 4},
        {UWN,   NONE,        0, 0},
        {TYA,   Implicit,    1, 2},
        {STA,   Absolute_Y,  3, 5},
        {TXS,   Implicit,    1, 2},
        {UWN,   NONE,        0, 0},
        {UWN,   NONE,        0, 0},
        {STA,   Absolute_X,  3, 5},
        {UWN,   NONE,        0, 0},
        {UWN,   NONE,        0, 0},
        /********************* 0xA0 *******************/
        {LDY,   Immediate,   2, 2},
        {LDA,   Indirect_Y,  2, 6},
        {LDX,   Immediate,   2, 2},
        {UWN,   NONE,        0, 0},
        {LDY,   Zero_Page,   2, 3},
        {LDA,   Zero_Page,   2, 3},
        {LDX,   Zero_Page,   2, 3},
        {UWN,   NONE,        0, 0},
        {TAY,   Implicit,    1, 2},
        {LDA,   Immediate,   2, 2},
        {TAX,   Implicit,    1, 2},
        {UWN,   NONE,        0, 0},
        {LDY,   Absolute,    3, 4},
        {LDA,   Absolute,    3, 4},
        {LDX,   Absolute,    3, 4},
        {UWN,   NONE,        0, 0},
        /********************* 0xB0 *******************/
        {BCS,   Relative,    2, 2},
        {LDA,   Indirect_Y,  2, 5},
        {UWN,   NONE,        0, 0},
        {UWN,   NONE,        0, 0},
        {LDY,   Zero_Page_X, 2, 4},
        {LDA,   Zero_Page_X, 2, 4},
        {LDX,   Zero_Page_Y, 2, 4},
        {UWN,   NONE,        0, 0},
        {CLV,   Implicit,    1, 2},
        {LDA,   Absolute_Y,  3, 4},
        {TSX,   Implicit,    1, 2},
        {UWN,   NONE,        0, 0},
        {LDY,   Absolute_X,  3, 4},
        {LDA,   Absolute_X,  3, 4},
        {LDX,   Absolute_Y,  3, 4},
        {UWN,   NONE,        0, 0},
        /********************* 0xC0 *******************/
        {CPY,   Immediate,   2, 2},
        {CMP,   Indirect_X,  2, 6},
        {UWN,   NONE,        0, 0},
        {UWN,   NONE,        0, 0},
        {CPY,   Zero_Page,   2, 3},
        {CMP,   Zero_Page,   2, 3},
        {DEC,   Zero_Page,   2, 5},
        {UWN,   NONE,        0, 0},
        {INY,   Implicit,    1, 2},
        {CMP,   Immediate,   2, 2},
        {DEX,   Implicit,    1, 2},
        {UWN,   NONE,        0, 0},
        {CPY,   Absolute,    3, 4},
        {CMP,   Absolute,    3, 4},
        {DEC,   Absolute,    3, 6},
        {UWN,   NONE,        0, 0},
        /********************* 0xD0 *******************/
        {BNE,   Relative,    2, 2},
        {CMP,   Indirect_Y,  2, 5},
        {UWN,   NONE,        0, 0},
        {UWN,   NONE,        0, 0},
        {UWN,   NONE,        0, 0},
        {CMP,   Zero_Page_X, 2, 4},
        {DEC,   Zero_Page_X, 2, 6},
        {UWN,   NONE,        0, 0},
        {CLD,   Implicit,    1, 2},
        {CMP,   Absolute_Y,  3, 4},
        {UWN,   NONE,        0, 0},
        {UWN,   NONE,        0, 0},
        {UWN,   NONE,        0, 0},
        {CMP,   Absolute_X,  3, 4},
        {DEC,   Absolute_X,  3, 7},
        {UWN,   NONE,        0, 0},
        /********************* 0xE0 *******************/
        {CPX,   Immediate,   2, 2},
        {SBC,   Indirect_X,  2, 6},
        {UWN,   NONE,        0, 0},
        {UWN,   NONE,        0, 0},
        {CPX,   Zero_Page,   2, 3},
        {SBC,   Zero_Page,   2, 3},
        {INC,   Zero_Page,   2, 5},
        {UWN,   NONE,        0, 0},
        {INX,   Implicit,    1, 2},
        {SBC,   Immediate,   2, 2},
        {NOP,   Implicit,    1, 2},
        {UWN,   NONE,        0, 0},
        {CPX,   Absolute,    3, 4},
        {SBC,   Absolute,    3, 4},
        {INC,   Absolute,    3, 6},
        {UWN,   NONE,        0, 0},
        /********************* 0xF0 *******************/
        {BEQ,   Relative,    2, 1},
        {SBC,   Indirect_Y,  2, 5},
        {UWN,   NONE,        0, 0},
        {UWN,   NONE,        0, 0},
        {UWN,   NONE,        0, 0},
        {SBC,   Zero_Page_X, 2, 4},
        {INC,   Zero_Page_X, 2, 6},
        {UWN,   NONE,        0, 0},
        {SED,   Implicit,    1, 2},
        {SBC,   Absolute_Y,  3, 4},
        {UWN,   NONE,        0, 0},
        {UWN,   NONE,        0, 0},
        {UWN,   NONE,        0, 0},
        {SBC,   Absolute_X,  3, 4},
        {INC,   Absolute_X,  3, 7},
        //Emulator debug instruction point to debug str
        {LOG, Absolute,    0, 0}
};

#define STACK_PTR_SPOS (0xFD)
#define NMI_VECTOR (0xFFFA)
#define RESET_VECTOR (0xFFFC)
#define IRQ_BRK_VECTOR (0xFFFE)
#define DEFAULT_STATUS_VAL  (0x34)


#define INES_NZS(cpu, val) \
{                     \
   ines_cpu_state(cpu,ZERO,val==0); \
   ines_cpu_state(cpu,NEGATIVE,(val>>7)==1);\
}                      \


#define INES_CPU_PAGE_CROSS(console, base, addr, offset) { \
    if ((base & 0xFF00) != (addr & 0xFF00)) {  \
        console->cpu->cycle += offset;              \
        INES_CPU_CLOCK_SYNC(console,offset)         \
    }                                          \
}                                              \

#define INES_CPU_STATE(cpu, bit) \
    ((cpu->p & (1<<bit))!=0);        \
                                 \

#define INES_CPU_STATE_SET_NEG(cpu, c) \
    ines_cpu_state(cpu, NEGATIVE, ((c) & 0x80) == 0x80); \


#define INES_CPU_CLOCK_SYNC(console, cycle) \
{                                           \
    ines_ppu_run(console, cycle);           \
    console->cpu->synchronized += cycle;       \
}                                           \


static uint16 ines_ops_decode_addr(NesConsole *console, AddressMode mode, bool checkPageCross) {
    uint16 b;
    CPU *cpu = console->cpu;
    if (mode == Immediate || mode == Zero_Page || mode == Relative) {
        b = cpu->pc;
        if (mode == Zero_Page) {
            b = ines_bus_read(console, b);
        }
    } else if (mode == Zero_Page_X || mode == Zero_Page_Y) {
        b = ines_bus_read(console, cpu->pc);
        if (mode == Zero_Page_X) {
            b = b + cpu->x;
        } else {
            b = b + cpu->y;
        }
    } else if (mode == Absolute || mode == Absolute_X || mode == Absolute_Y) {
        b = ines_bus_read_uint16(console, cpu->pc);
        uint8 offset = 0;
        if (mode == Absolute_X) {
            offset = cpu->x;
        } else if (mode == Absolute_Y) {
            offset = cpu->y;
        }
        uint16 addr = b + offset;
        if (checkPageCross) {
            INES_CPU_PAGE_CROSS(console, b, addr, 1)
        }
        b = addr;
    } else if (mode == Indirect) {
        // JMP is the only 6502 instruction to support indirection. The instruction contains a 16 bit
        // address which identifies the location of the least significant byte of another 16 bit memory
        // address which is the real target of the instruction.
        // For example if location $0120 contains $FC and location $0121 contains $BA then the instruction
        // JMP ($0120) will cause the next instruction execution to occur at $BAFC (e.g. the contents of
        // $0120 and $0121).
        b = ines_bus_read_uint16(console, cpu->pc);
        b = ines_bus_read_uint16(console, b);
    } else if (mode == Indirect_X) {
        // Indexed indirect addressing is normally used in conjunction with a table of address held on zero
        // page. The address of the table is taken from the instruction and the X register added to it
        // (with zero page wrap around) to give the location of the least significant byte of the target
        // address.
        b = ines_bus_read(console, cpu->pc);
        b = b + cpu->x;
    } else if (mode == Indirect_Y) {
        // Indirect indirect addressing is the most common indirection mode used on the 6502. In instruction
        // contains the zero page location of the least significant byte of 16 bit address. The Y register
        // is dynamically added to this value to generated the actual target address for operation.
        b = ines_bus_read(console, cpu->pc);
        b = ines_bus_read_uint16(console, b);
        uint16 addr = b + cpu->y;
        if (checkPageCross) {
            INES_CPU_PAGE_CROSS(console, b, addr, 1)
        }
        b = addr;
    } else {
        b = 0;
    }
    INES_CPU_CLOCK_SYNC(console, 1)
    return b;
}

static inline uint8 ines_ops_operand0(NesConsole *console, AddressMode mode, bool checkPageCross) {
    if (mode == Accumulator) {
        return console->cpu->a;
    }
    uint16 addr = ines_ops_decode_addr(console, mode, True);
    return ines_bus_read(console, addr);
}


static inline uint8 ines_ops_operand(NesConsole *console, AddressMode mode) {
    return ines_ops_operand0(console, mode, False);
}


static inline void ines_ops_write(NesConsole *console, AddressMode mode, uint8 b) {
    if (mode == Accumulator) {
        console->cpu->a = b;
    } else {
        uint16 addr = ines_ops_decode_addr(console, mode, False);
        ines_bus_write(console, addr, b);
    }
}


static inline void ines_cpu_state(CPU *cpu, StatusBit bit, bool set) {
    uint8 tmp = cpu->p;
    if (set) {
        tmp |= (1 << bit);
    } else {
        tmp &= ~(1 << bit);
    }
    cpu->p = tmp;
}